<template>
    <MyArticle title="Java学习笔记之七：集合(2)" date="2024年03月17日">
        <div class="_article-content">
            <p>通过使用视图可以得到其他实现了Collection接口或者Map接口的对象。</p>
            <p>Java9引入了一些静态方法，可以生成给定元素的集或列表，以及给定键/值对的映射：</p>
            <pre><code>List&lt;String&gt; names = List.of("Peter", "Paul", "Mary");
Set&lt;Integer&gt; numbers = Set.of(1, 2, 3, 4, 5);
Map&lt;String, Integer&gt; scores = Map.of("Peter", 100, "Paul", 200, "Mary", 300);</code></pre>
            <p>元素、键或值不能为null，集和映射键不能重复。对于这些集和映射中的迭代顺序，并没有任何保证。</p>
            <p>这些集合对象是不可修改的，如果试图改变它们的内容，会导致一个UnsupportedOperationException异常。如果需要一个可更改的集合，可以把这个不可修改的集合传递到构造器：</p>
            <pre><code>var names = new ArrayList&lt;&gt;(List.of("Peter", "Paul", "Mary"));</code></pre>
            <p>下面的调用将创建一个包含100个字符串的List，每个字符串包含“DEFAULT”：</p>
            <pre><code>List&lt;String&gt; arr = Collections.nCopies(100, "DEFAULT");</code></pre>
            <p>为了建立一个集合的不可修改的副本，可以使用集合类型的copyOf方法：</p>
            <pre><code>ArrayList&lt;String&gt; names = ...
Set&lt;String&gt; nameSet = Set.copyOf(names);
List&lt;String&gt; nameList = List.copyOf(names);</code></pre>
            <p>每个copyOf方法都会建立集合的一个副本，如果修改了原集合，这个副本不受影响。</p>
            <p>如果原集合恰好是不可修改的，而且类型正确，copyOf则会直接返回原集合：</p>
            <pre><code>Set&lt;String&gt; names = Set.Of("Peter", "Paul", "Mary");
Set&lt;String&gt; nameSet = Set.copyOf(names); // nameSet == names</code></pre>
            <p>Collections类还有一些方法可以生成集合的不可修改的视图，这些视图对现有集合增加了一个运行时检查。如果检测到试图修改不可修改的集合，就抛出一个异常。如果原集合改变，视图会反映这些变化。</p>
            <pre><code>Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableMap
Collections.unmodifiableNavigableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableNavigableMap
Collections.unmodifiableSortedMap</code></pre>
            <p>每个方法都定义为处理一个接口，Collections.unmodifiableList可以处理ArrayList、LinkedList或者实现了List接口的任何其他类。</p>
            <pre><code>var staff = new LinkedList&lt;String&gt;();
......
lookAt(Collections.unmodifiableList(staff));</code></pre>
            <p>不可修改的视图并不会让集合本身变为不可变。视图只是包装了接口而不是具体的集合对象，所以只能访问接口中定义的方法。</p>
            <p>可以为很多集合建立子范围视图：</p>
            <pre><code>List&lt;Employee&gt; group = staff.subList(10, 20);</code></pre>
            <p>第一个索引包含在内，而不包含第二个索引。可以对子范围应用任何操作，而且这些操作会自动反映到整个列表，例如，可以删除子范围：</p>
            <pre><code>group.clear(); // 元素会自动地从staff删除</code></pre>
            <p>对于有序集和映射，可以使用排序顺序而不是元素位置建立子范围，SortedSet接口声明了3个方法：</p>
            <pre><code>SortedSet&lt;E&gt; subSet(E from, E to);
SortedSet&lt;E&gt; headSet(E to);
SortedSet&lt;E&gt; tailSet(E from);</code></pre>
            <p>这些方法将返回大于等于from且小于to的所有元素构成的子集。SortedMap也有类似的接口：</p>
            <pre><code>SortedMap&lt;E&gt; subMap(E from, E to);
SortedMap&lt;E&gt; headMap(E to);
SortedMap&lt;E&gt; tailMap(E from);</code></pre>
            <p>这些方法会返回映射的视图，其中包含落在指定范围内的键相应的所有元素。</p>
            <p>Java6引人的NavigableSet接口允许对这些子范围操作有更多控制，可以指定是否包括边界：</p>
            <pre><code>NavigableSet&lt;E&gt; subSet(E from, boolean fromInclusive, E to, boolean toInclusive);
NavigableSet&lt;E&gt; headSet(E to, boolean toInclusive);
NavigableSet&lt;E&gt; tailSet(E from, boolean fromInclusive);</code></pre>
            <p>检查型视图（Checked View）是集合框架中用于增强类型安全性的重要机制，它通过动态类型检查，确保集合操作时元素的类型符合预期：</p>
            <pre><code>List&lt;String&gt; safeList = Collections.checkedList(new ArrayList<>(), String.class);
safeList.add("Test"); // 正常
safeList.add(123); // 抛出 ClassCastException</code></pre>
            <p>Java提供Collections类的多个方法创建检查型视图：</p>
            <pre><code>checkedCollection()
checkedList()
checkedMap()
checkedNavigableMap()
checkedNavigableSet()
checkedQueue()
checkedSet()
checkedSortedMap()
checkedSortedSet()</code></pre>
            <p>如果从多个线程访问集合，就必须确保集合不会被意外地破坏。如果一个线程试图为散列表增加元素，同时另一个线程正在对元素进行再散列，则结果将混乱。</p>
            <p>类库的设计者使用视图机制来确保常规集合是线程安全的，例如Collections类的静态synchronizedMap方法可以将任何一个映射转换成有同步访问方法的Map：</p>
            <pre><code>var map = Collections.synchronizedMap(new HashMap&lt;String, Employee&gt;());</code></pre>
            <p>这样就可以从多线程访问这个Map对象，get和put方法是同步的，即每个方法调用必须完全结束，另一个线程才能调用另一个方法。</p>
            <p>Collections类中的sort方法可以对实现了List接口的集合进行排序：</p>
            <pre><code>var staff = new LinkedList&lt;String&gt;();
Collections.sort(staff); // 假定列表元素实现了Comparable接口</code></pre>
            <p>也可以传入Comparator对象：</p>
            <pre><code>staff.sort(Comparator.comparingDouble(Employee::getSalary));
staff.sort(Comparator.comparingDouble(Employee::getSalary).reversed()); // 倒序</code></pre>
            <p>集合类库中使用的排序算法比快速排序要慢一些，优点是它是稳定的算法，不会改变相等元素的顺序。</p>
            <p>Collections类的shuffle算法，其功能与排序刚好相反，它会随机地混排列表中元素的顺序：</p>
            <pre><code>ArrayList&lt;Card&gt; cards = ....;
Collections.shuffle(cards);</code></pre>
            <p>Collections类的binarySearch方法实现了二分查找算法，注意集合必须有序，否则算法会返回错误的答案。</p>
            <pre><code>i = Collections.binarySearch(c, element);
i = Collections.binarySearch(c, element, comparator); // 使用自定义的比较器</code></pre>
            <p>如果返回一个非负的值，这表示匹配对象的索引；如果返回负值，则表示没有匹配的元素。</p>
            <p>负值可以用来计算应该将element插入集合的哪个位置，以保持集合的有序性，插入的位置是：</p>
            <pre><code>insertionPoint = -i - 1;</code></pre>
            <p>只有采用随机访问方式，二分查找才有意义。如果为binarySearch算法提供了一个链表，它将退化为线性查找。</p>
            <p>批操作：</p>
            <pre><code>coll1.removeAll(coll2); // 将从coll1中删除coll2中出现的所有元素
coll1.retainAll(coll2); // 将从coll1中删除所有未在coll2中出现的元素</code></pre>
            <p>求两个集合的交集：</p>
            <pre><code>var result = new HashSet&lt;String&gt;(firstSet);
result.retainAll(secondSet);</code></pre>
            <p>集合与数组之间的转换：</p>
            <pre><code>// 将数组转换为集合
String[] names = {"Tom", "Dick", "Harry"};
List&lt;String&gt; list = List.of(names);

// 从集合到数组
Object[] names = staff.toArray();
String[] names = staff.toArray(new String[0]); // JDK11之前
String[] names = staff.toArray(String[]::new); // 传入一个数组构造器表达式</code></pre>
            <h3 class="title">属性映射Properties</h3>
            <p>是一个特殊类型的映射结构：它的键与值都是字符串；这个映射可以很容易地保存到文件以及从文件加载；有一个二级表存放默认值。</p>
            <pre><code>var setting = new Properties();
settings.setProperty("width", "640.00");
settings.setProperty("filename", "/home/user/data.txt");

// 保存到一个文件中
var out = new FileWriter("settings.properties", StandardCharsets.UTF_8);
setting.store(out, "Settings");

// 从文件中加载
var in = new FileReader("settings.properties", StandardCharsets.UTF_8);
setting.load(in);</code></pre>
            <p>读取属性可以提供默认值：</p>
            <pre><code>String filename = settings.getProperty("filename", "");</code></pre>
            <p>也可以将所有默认值都放在一个二级属性映射中，井在主属性映射的构造器中提供这个二级映射：</p>
            <pre><code>var defaultSetting = new Properties();
defaultSetting.setProperty("width", "640.00");
defaultSetting.setProperty("filename", "/home/user/data.txt");
var setting = new Properties(defaultSetting);</code></pre>
            <p>System.getProperties方法会生成一个Properties对象来描述系统信息：</p>
            <pre><code>Properties props = System.getProperties();
String userHome = props.getProperty("user.home");
String jdkVersion = props.getProperty("java.version");
System.out.println("userHome：" + userHome); // userHome：C:\Users\Administrator
System.out.println("jdkVersion：" + jdkVersion); // jdkVersion：17</code></pre>
            <h3 class="title">栈Stack</h3>
            <pre><code>E push(E item); // 将item压入栈并返回item
E pop(); // 弹出栈顶元素并返回
E peek(); // 返回栈顶元素，但不弹出</code></pre>
            <h3 class="title">位集BitSet</h3>
            <p>BitSet类会存储一个位序列。由于位集将位包装在字节里，因此使用位集要比使用Boolean对象的ArrayList高效得多。</p>
            <p>BitSet类提供了一个用于读取、设置或重置各个位的很方便的接口。使用这个接口可以避免掩码和其他调整位的操作。</p>
            <pre><code>bucketOfBits.get(i); // 如果第i位处于"开"状态，就返回true；否则返回false
bucketOfBits.set(i); // 第i位设置为"开"状态
bucketOfBits.clear(i); // 第i位设置为"关"状态
            </code></pre>
            <p>统计质数：</p>
            <pre><code>long start = System.currentTimeMillis();
int num = 2000000;
BitSet bitSet = new BitSet(num + 1);
int i = 2;
for (; i <= num; i++) {
    bitSet.set(i);
}
int j = 0;
for (i = 2; i * i < num; i++) {
    if (bitSet.get(i)) {
        for (j = i * i; j <= num; j = j + i) {
            bitSet.clear(j);
        }
    }
}
long end = System.currentTimeMillis();
System.out.println("耗时：" + (end - start));
int count = 0;
for (i = 2; i <= num; i++) {
    if (bitSet.get(i)) {
        count ++;
    }
}
System.out.println("质数总计：" + count);</code></pre>
        </div>
    </MyArticle>
</template>
